/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/firmware.h>
#include <linux/cdev.h>
#include <linux/delay.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>

#include "mc.h"
#include "rlc.h"
#include "ih.h"
#include "fence.h"
#include "ring.h"
#include "dmas.h"
#include "ba.h"
#include "cps.h"
#include "gpu.h"
#include "drv.h"

#include "ucode.h"

#include "regs.h"

#define TAHITI_MC2_FW_DWS	7808
#define PITCAIRN_MC2_FW_DWS	7775
#define VERDE_MC2_FW_DWS	7875
#define OLAND_MC2_FW_DWS	7863
MODULE_FIRMWARE("radeon/TAHITI_mc2.bin");
MODULE_FIRMWARE("radeon/PITCAIRN_mc2.bin");
MODULE_FIRMWARE("radeon/VERDE_mc2.bin");
MODULE_FIRMWARE("radeon/OLAND_mc2.bin");

u32 mc_clients_blackout_mode(struct pci_dev *dev)
{
	u32 mc_shared_blackout_ctl;

	mc_shared_blackout_ctl = rr32(dev, MC_SHARED_BLACKOUT_CTL);
	return get(MSBC_MODE, mc_shared_blackout_ctl);
}

void mc_clients_blackout_dis(struct pci_dev *dev)
{
	u32 mc_shared_blackout_ctl;

	mc_shared_blackout_ctl = rr32(dev, MC_SHARED_BLACKOUT_CTL);
	mc_shared_blackout_ctl &= ~MSBC_MODE;
	mc_shared_blackout_ctl |= set(MSBC_MODE, MSBC_MODE_DIS);
	wr32(dev, mc_shared_blackout_ctl, MC_SHARED_BLACKOUT_CTL);
}

void mc_clients_blackout_ena(struct pci_dev *dev)
{
	u32 mc_shared_blackout_ctl;

	mc_shared_blackout_ctl = rr32(dev, MC_SHARED_BLACKOUT_CTL);
	mc_shared_blackout_ctl &= ~MSBC_MODE;
	mc_shared_blackout_ctl |= set(MSBC_MODE, MSBC_MODE_ENA);
	wr32(dev, mc_shared_blackout_ctl, MC_SHARED_BLACKOUT_CTL);
}

/* override the memory configuration */
void mc_addr_cfg_compute(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	u32 arb_ram_cfg;
	u32 cols_n;

	dd = pci_get_drvdata(dev);
	
	arb_ram_cfg = rr32(dev, MC_ARB_RAM_CFG);
	cols_n = get(MARC_COLS_N, arb_ram_cfg);
	
	dd->mem_row_sz_kb = (4 *(1 << (8 + cols_n))) / 1024;
	if (dd->mem_row_sz_kb > 4)
		dd->mem_row_sz_kb = 4;

	dd->addr_cfg = dd->cfg.addr_best;
	dd->addr_cfg &= ~GAC_ROW_SZ;
	switch (dd->mem_row_sz_kb) {
	case 1:
	default:
		dd->addr_cfg |= set(GAC_ROW_SZ, 0);
		break;
	case 2:
		dd->addr_cfg |= set(GAC_ROW_SZ, 1);
		break;
	case 4:
		dd->addr_cfg |= set(GAC_ROW_SZ, 2);
		break;
	}
}

#define SI_IO_MC_REGS_N 36

static const u32 tahiti_io_mc_regs[SI_IO_MC_REGS_N][2] = {
	{0x0000006f, 0x03044000},
	{0x00000070, 0x0480c018},
	{0x00000071, 0x00000040},
	{0x00000072, 0x01000000},
	{0x00000074, 0x000000ff},
	{0x00000075, 0x00143400},
	{0x00000076, 0x08ec0800},
	{0x00000077, 0x040000cc},
	{0x00000079, 0x00000000},
	{0x0000007a, 0x21000409},
	{0x0000007c, 0x00000000},
	{0x0000007d, 0xe8000000},
	{0x0000007e, 0x044408a8},
	{0x0000007f, 0x00000003},
	{0x00000080, 0x00000000},
	{0x00000081, 0x01000000},
	{0x00000082, 0x02000000},
	{0x00000083, 0x00000000},
	{0x00000084, 0xe3f3e4f4},
	{0x00000085, 0x00052024},
	{0x00000087, 0x00000000},
	{0x00000088, 0x66036603},
	{0x00000089, 0x01000000},
	{0x0000008b, 0x1c0a0000},
	{0x0000008c, 0xff010000},
	{0x0000008e, 0xffffefff},
	{0x0000008f, 0xfff3efff},
	{0x00000090, 0xfff3efbf},
	{0x00000094, 0x00101101},
	{0x00000095, 0x00000fff},
	{0x00000096, 0x00116fff},
	{0x00000097, 0x60010000},
	{0x00000098, 0x10010000},
	{0x00000099, 0x00006000},
	{0x0000009a, 0x00001000},
	{0x0000009f, 0x00a77400}
};

static const u32 pitcairn_io_mc_regs[SI_IO_MC_REGS_N][2] = {
	{0x0000006f, 0x03044000},
	{0x00000070, 0x0480c018},
	{0x00000071, 0x00000040},
	{0x00000072, 0x01000000},
	{0x00000074, 0x000000ff},
	{0x00000075, 0x00143400},
	{0x00000076, 0x08ec0800},
	{0x00000077, 0x040000cc},
	{0x00000079, 0x00000000},
	{0x0000007a, 0x21000409},
	{0x0000007c, 0x00000000},
	{0x0000007d, 0xe8000000},
	{0x0000007e, 0x044408a8},
	{0x0000007f, 0x00000003},
	{0x00000080, 0x00000000},
	{0x00000081, 0x01000000},
	{0x00000082, 0x02000000},
	{0x00000083, 0x00000000},
	{0x00000084, 0xe3f3e4f4},
	{0x00000085, 0x00052024},
	{0x00000087, 0x00000000},
	{0x00000088, 0x66036603},
	{0x00000089, 0x01000000},
	{0x0000008b, 0x1c0a0000},
	{0x0000008c, 0xff010000},
	{0x0000008e, 0xffffefff},
	{0x0000008f, 0xfff3efff},
	{0x00000090, 0xfff3efbf},
	{0x00000094, 0x00101101},
	{0x00000095, 0x00000fff},
	{0x00000096, 0x00116fff},
	{0x00000097, 0x60010000},
	{0x00000098, 0x10010000},
	{0x00000099, 0x00006000},
	{0x0000009a, 0x00001000},
	{0x0000009f, 0x00a47400}
};

static const u32 verde_io_mc_regs[SI_IO_MC_REGS_N][2] = {
	{0x0000006f, 0x03044000},
	{0x00000070, 0x0480c018},
	{0x00000071, 0x00000040},
	{0x00000072, 0x01000000},
	{0x00000074, 0x000000ff},
	{0x00000075, 0x00143400},
	{0x00000076, 0x08ec0800},
	{0x00000077, 0x040000cc},
	{0x00000079, 0x00000000},
	{0x0000007a, 0x21000409},
	{0x0000007c, 0x00000000},
	{0x0000007d, 0xe8000000},
	{0x0000007e, 0x044408a8},
	{0x0000007f, 0x00000003},
	{0x00000080, 0x00000000},
	{0x00000081, 0x01000000},
	{0x00000082, 0x02000000},
	{0x00000083, 0x00000000},
	{0x00000084, 0xe3f3e4f4},
	{0x00000085, 0x00052024},
	{0x00000087, 0x00000000},
	{0x00000088, 0x66036603},
	{0x00000089, 0x01000000},
	{0x0000008b, 0x1c0a0000},
	{0x0000008c, 0xff010000},
	{0x0000008e, 0xffffefff},
	{0x0000008f, 0xfff3efff},
	{0x00000090, 0xfff3efbf},
	{0x00000094, 0x00101101},
	{0x00000095, 0x00000fff},
	{0x00000096, 0x00116fff},
	{0x00000097, 0x60010000},
	{0x00000098, 0x10010000},
	{0x00000099, 0x00006000},
	{0x0000009a, 0x00001000},
	{0x0000009f, 0x00a37400}
};

static const u32 oland_io_mc_regs[SI_IO_MC_REGS_N][2] = {
	{0x0000006f, 0x03044000},
	{0x00000070, 0x0480c018},
	{0x00000071, 0x00000040},
	{0x00000072, 0x01000000},
	{0x00000074, 0x000000ff},
	{0x00000075, 0x00143400},
	{0x00000076, 0x08ec0800},
	{0x00000077, 0x040000cc},
	{0x00000079, 0x00000000},
	{0x0000007a, 0x21000409},
	{0x0000007c, 0x00000000},
	{0x0000007d, 0xe8000000},
	{0x0000007e, 0x044408a8},
	{0x0000007f, 0x00000003},
	{0x00000080, 0x00000000},
	{0x00000081, 0x01000000},
	{0x00000082, 0x02000000},
	{0x00000083, 0x00000000},
	{0x00000084, 0xe3f3e4f4},
	{0x00000085, 0x00052024},
	{0x00000087, 0x00000000},
	{0x00000088, 0x66036603},
	{0x00000089, 0x01000000},
	{0x0000008b, 0x1c0a0000},
	{0x0000008c, 0xff010000},
	{0x0000008e, 0xffffefff},
	{0x0000008f, 0xfff3efff},
	{0x00000090, 0xfff3efbf},
	{0x00000094, 0x00101101},
	{0x00000095, 0x00000fff},
	{0x00000096, 0x00116fff},
	{0x00000097, 0x60010000},
	{0x00000098, 0x10010000},
	{0x00000099, 0x00006000},
	{0x0000009a, 0x00001000},
	{0x0000009f, 0x00a17730}
};

long mc_ucode_load(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	long r;
	char *fw_version_str;

	dd = pci_get_drvdata(dev);
	dd->mc_fw_dws = 0;

	switch (dd->family) {
	case TAHITI:
		dd->mc_fw_dws = TAHITI_MC2_FW_DWS;
		break;
	case PITCAIRN:
		dd->mc_fw_dws = PITCAIRN_MC2_FW_DWS;
		break;
	case VERDE:
		dd->mc_fw_dws = VERDE_MC2_FW_DWS;
		break;
	case OLAND:
		dd->mc_fw_dws = OLAND_MC2_FW_DWS;
	}
	fw_version_str = "mc2";

	r = ucode_load(dev, &(dd->mc_fw), fw_version_str, dd->mc_fw_dws);
	return r;
}

void mc_ucode_program(struct pci_dev *dev)
{
	const __be32 *fw_data;
	u32 running;
	u32 blackout;
	u32 *io_mc_regs;
	u32 i;
	struct dev_drv_data *dd;

	dd = pci_get_drvdata(dev);

	io_mc_regs = NULL;
	switch (dd->family) {
	case TAHITI:
		io_mc_regs = (u32 *)&tahiti_io_mc_regs[0][0];
		break;
	case PITCAIRN:
		io_mc_regs = (u32 *)&pitcairn_io_mc_regs[0][0];
		break;
	case VERDE:
		io_mc_regs = (u32 *)&verde_io_mc_regs[0][0];
		break;
	case OLAND:
		io_mc_regs = (u32 *)&oland_io_mc_regs[0][0];
	}

	running = rr32(dev, MC_SEQ_SUP_CTL) & MSSC_RUN_MASK;
	blackout = 0;

	if (running == 0) {
		/* this is a reminder */
		if (running) {
			blackout = rr32(dev, MC_SHARED_BLACKOUT_CTL);
			wr32(dev, blackout | 1, MC_SHARED_BLACKOUT_CTL);
		}

		/* reset the engine and set to writable */
		wr32(dev, 0x00000008, MC_SEQ_SUP_CTL);
		wr32(dev, 0x00000010, MC_SEQ_SUP_CTL);

		/* load mc io regs */
		for (i = 0; i < SI_IO_MC_REGS_N; ++i) {
			wr32(dev, io_mc_regs[i * 2], MC_SEQ_IO_DBG_IDX);
			wr32(dev, io_mc_regs[i * 2 + 1], MC_SEQ_IO_DBG_DATA);
		}
		/* load the mc ucode */
		fw_data = (const __be32 *)dd->mc_fw->data;
		for (i = 0; i < dd->mc_fw_dws; ++i)
			wr32(dev, be32_to_cpup(fw_data++), MC_SEQ_SUP_PGM);

		/* put the engine back into the active state */
		wr32(dev, 0x00000008, MC_SEQ_SUP_CTL);
		wr32(dev, 0x00000004, MC_SEQ_SUP_CTL);
		wr32(dev, 0x00000001, MC_SEQ_SUP_CTL);

		/* wait for memory training to complete (100 ms timeout) */
		for (i = 0; i < USEC_TIMEOUT; ++i) {/* memory pattern D0 */
			if (rr32(dev, MC_SEQ_TRAIN_WAKEUP_CTL)
							& MSTWC_TRAIN_DONE_D0)
				break;
			udelay(1);
		}
		for (i = 0; i < USEC_TIMEOUT; ++i) {/* memory pattern D1 */
			if (rr32(dev, MC_SEQ_TRAIN_WAKEUP_CTL)
							& MSTWC_TRAIN_DONE_D1)
				break;
			udelay(1);
		}

		/* this is a reminder */
		if (running)
			wr32(dev, blackout, MC_SHARED_BLACKOUT_CTL);
	}
}

long mc_wait_for_idle(struct pci_dev *dev)
{
	u32 i;

	for (i = 0; i < USEC_TIMEOUT; ++i) {
		if (!(rr32(dev, SRBM_STATUS) & SS_MC_STATUS))
			return 0;
		udelay(1);
	}
	return -1;
}

long mc_program(struct pci_dev *dev)
{
	struct dev_drv_data *dd;
	u32 mc_vram_location;
	u64 vram_start;
	u64 vram_end;

	dd = pci_get_drvdata(dev);

	vram_start = dd->vram.mng.s;
	vram_end = vram_start + dd->vram.mng.sz - 1;

	if (mc_wait_for_idle(dev)) {
		dev_warn(&dev->dev,
				"mc_program:stop:wait for mc idle timed out\n");
		return -SI_ERR;
	}

	wr32(dev, GPU_PAGE_IDX(dd->vram.scratch_page),
						MC_VM_SYS_APER_DEFAULT_ADDR);
	wr32(dev, GPU_PAGE_IDX(vram_start), MC_VM_SYS_APER_LOW_ADDR);
	wr32(dev, GPU_PAGE_IDX(vram_end), MC_VM_SYS_APER_HIGH_ADDR);

	/*
	 * MC_VRAM_LOCATION: map VRAM in GPU address space
	 *   [15:8] last gpu address 16MB block index 
	 *   [7:0]  first gpu address 16MB block index
	 */
	mc_vram_location = ((vram_end >> 24) & 0xffff) << 16;
	mc_vram_location |= ((vram_start >> 24) & 0xffff);
	wr32(dev, mc_vram_location, MC_VRAM_LOCATION);

	/* no agp aperture in physical addresses */
	wr32(dev, 0, MC_AGP_BASE);
	wr32(dev, 0x0fffffff, MC_AGP_TOP);
	wr32(dev, 0x0fffffff, MC_AGP_BOT);

	if (mc_wait_for_idle(dev)) {
		dev_warn(&dev->dev,
			"mc_program:start:wait for mc idle timed out\n");
		return -SI_ERR;
	}
	return 0;
}


static u32 regs_mc_cg[] = {
	MC_HUB_MISC_HUB_CG,
	MC_HUB_MISC_SIP_CG,
	MC_HUB_MISC_VM_CG,
	MC_XPB_CG,
	ATC_MISC_CG,
	MC_CITF_MISC_WR_CG,
	MC_CITF_MISC_RD_CG,
	MC_CITF_MISC_VM_CG,
	VM_L2_CG,
};

void mc_mgcg_dis(struct pci_dev *dev)
{
	u32 reg;

	for (reg = 0; reg < ARRAY_SIZE(regs_mc_cg); ++reg) {
		u32 cur;
		u32 want;

		cur = rr32(dev, regs_mc_cg[reg]);
		want = cur & ~VLC_MC_CG_ENA;
		if (cur != want)
			wr32(dev, want, regs_mc_cg[reg]);
	}
}

void mc_mgcg_ena(struct pci_dev *dev)
{
	u8 reg;
	
	for (reg = 0; reg < ARRAY_SIZE(regs_mc_cg); ++reg) {
		u32 cur;
		u32 want;

		cur = rr32(dev, regs_mc_cg[reg]);
		want = cur | VLC_MC_CG_ENA;
		if (cur != want)
			wr32(dev, want, regs_mc_cg[reg]);
	}
}

void mc_ls_dis(struct pci_dev *dev)
{
	u32 reg;

	for (reg = 0; reg < ARRAY_SIZE(regs_mc_cg); ++reg) {
		u32 cur;
		u32 want;

		cur = rr32(dev, regs_mc_cg[reg]);
		want = cur & ~VLC_MC_LS_ENA;
		if (cur != want)
			wr32(dev, want, regs_mc_cg[reg]);
	}
}

void mc_ls_ena(struct pci_dev *dev)
{
	u8 reg;

	for (reg = 0; reg < ARRAY_SIZE(regs_mc_cg); ++reg) {
		u32 cur;
		u32 want;

		cur = rr32(dev, regs_mc_cg[reg]);
		want = cur | VLC_MC_LS_ENA;
		if (cur != want)
			wr32(dev, want, regs_mc_cg[reg]);
	}
}

u8 mc_lb_pwr_supported(struct pci_dev *dev)
{
	u32 mc_seq_misc_0;

	/* Enable LBPW only for DDR3 */
	mc_seq_misc_0 = rr32(dev, MC_SEQ_MISC_0);
	if (get(MSM0_GDDR5, mc_seq_misc_0) == 0xb0000000)
		return 1;
	return 0;
}

u8 mc_vram_is_gddr5(struct pci_dev *dev)
{
	u32 mc_seq_misc_0;
	u32 gddr5;

	mc_seq_misc_0 = rr32(dev, MC_SEQ_MISC_0);

	gddr5 = get(MSM0_GDDR5, mc_seq_misc_0);
	if (gddr5 == MSM0_GDDR5_VAL)
		return 1;
	return 0;
}
